#include "krpc.h"
#include "bencode.h"
#include "epoll_loop.h"
#include "routing_table.h"
#include "socket.h"
#include "stream.h"
#include "task.h"
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <functional>
#include <netinet/in.h>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <strings.h>
#include <sys/epoll.h>
#include <sys/socket.h>
namespace cobt {
std::string sockaddr_to_string(const SocketAddress &addr) {
  return std::string(reinterpret_cast<const char *>(addr.get_addr6()),
                     sizeof(sockaddr_in6));
}

void sendto(int fd, std::span<char> buf, const SocketAddress &addr, int flags) {
  int res;
  while (buf.size()) {
    res = ::sendto(fd, buf.data(), buf.size(), flags, addr.get_addr(),
                   addr.get_addr_len());
    if (res >= 0) {
      buf = buf.subspan(res);
      continue;
    }
    if (errno == EAGAIN) {
      continue;
    }
    break;
    // std::cout << strerror(errno) << std::endl;
    // throw std::runtime_error("sendto_async: error " +
    //                          std::string(addr.get_ipstr()) + " " +
    //                          std::to_string(addr.get_port()));
  }
}

Task<>
recvfrom_async(int fd, int flags,
               std::function<void(std::span<char>, const SocketAddress &)> cb) {
  int res = 0;
  sockaddr_in6 addr;
  bzero(&addr, sizeof(addr));
  socklen_t addrlen = sizeof(addr);
  char buf[STREAMBUFSIZE];
  EventAwaiter awaiter(g_loop, fd, EPOLLIN | EPOLLET, 3);
  while (true) {
    res = recvfrom(fd, buf, sizeof(buf), flags, (sockaddr *)&addr, &addrlen);
    if (res >= 0) {
      cb(std::span<char>(buf, res), SocketAddress((sockaddr *)&addr, addrlen));
      continue;
    }
    if (errno != EAGAIN) {
      throw std::runtime_error("recvfrom_async: error");
    }
    co_await awaiter;
  }
}

std::optional<SocketAddress> parse_compact_addr(const std::string &compact) {
  if (compact.size() == 6) {
    sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    memcpy(&addr.sin_addr, compact.data(), sizeof(addr.sin_addr));
    memcpy(&addr.sin_port, compact.data() + 4, sizeof(addr.sin_port));
    return SocketAddress{(sockaddr *)&addr, sizeof(addr)};
  }
  if (compact.size() == 18) {
    sockaddr_in6 addr;
    bzero(&addr, sizeof(addr));
    addr.sin6_family = AF_INET6;
    memcpy(&addr.sin6_addr, compact.data(), sizeof(addr.sin6_addr));
    memcpy(&addr.sin6_port, compact.data() + 16, sizeof(addr.sin6_port));
    return SocketAddress{(sockaddr *)&addr, sizeof(addr)};
  }
  return std::nullopt;
}
std::optional<DHTNode> parse_compact(const std::string &compact) {
  if (compact.size() == 26 || compact.size() == 38) {
    return DHTNode{compact.substr(0, 20),
                   parse_compact_addr(compact.substr(20)).value()};
  }
  return std::nullopt;
}

void ping(int fd, const SocketAddress &addr, const std::string &id) {
  BEncodeData req(BMap{{{"t", BEncodeData("aa")},
                        {"y", BEncodeData("q")},
                        {"q", BEncodeData("ping")},
                        {"a", BEncodeData(BMap{{"id", BEncodeData(id)}})}}});
  std::string send_buf = req.dump();
  sendto(fd, std::span<char>(send_buf.data(), send_buf.size()), addr, 0);
}

void find_node(int fd, const SocketAddress &addr, const std::string &id,
               const std::string &target) {
  BEncodeData req(
      BMap{{"t", BEncodeData("aa")},
           {"y", BEncodeData("q")},
           {"q", BEncodeData("find_node")},
           {"a", BEncodeData(BMap{{"id", BEncodeData(id)},
                                  {"target", BEncodeData(target)}})}});
  std::string send_buf = req.dump();
  sendto(fd, std::span<char>(send_buf.data(), send_buf.size()), addr, 0);
}

void get_peers(int fd, const SocketAddress &addr, const std::string &id,
               const std::string &info_hash) {
  BEncodeData req(
      BMap{{"t", BEncodeData("aa")},
           {"y", BEncodeData("q")},
           {"q", BEncodeData("get_peers")},
           {"a", BEncodeData(BMap{{"id", BEncodeData(id)},
                                  {"info_hash", BEncodeData(info_hash)}})}});
  std::string send_buf = req.dump();
  sendto(fd, std::span<char>(send_buf.data(), send_buf.size()), addr, 0);
}

} // namespace cobt